﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using YAPL.Language.Compiler.CodeGen.ReflectionEmitGenerator.AstGen;
using YAPL.Language.Core;
using YAPL.Util;
using Ast = YAPL.Language.Ast;
namespace YAPL.Language.Compiler.CodeGen.ReflectionEmitGenerator {
	/// <summary>
	/// Code generator that uses Reflection.Emit to build .net assembly from given ast
	/// </summary>
	public class ReCodeGenerator : CodeGenerator {
		//TODO remove crutch with Depth property
		#region _param keys
		public const string MODULE_BUILDER_KEY = "moduleBuilder";
		public const string ASSEMBLY_BUILDER_KEY = "assemblyuilder";
		public const string TYPE_BUILDER_KEY = "typeBuilder";
		public const string METHOD_BUILDER_KEY = "methodBuilder";
		public const string IL_GENERATOR_KEY = "ilGenerator";
		#endregion

		//dictionary of code generators for different expressions
		private readonly Dictionary<Type, ReExpressionGenerator> _generators = new Dictionary<Type, ReExpressionGenerator>();

		//list of function classes defined in program
		private readonly List<Type> _functionDefClasses = new List<Type>();

		//generator parameter hash
		private readonly Dictionary<string, Object> _params = new Dictionary<string, object>();

		private readonly Stack<string> _classScopeStack = new Stack<string>();
		public ReCodeGenerator(OutputInfo outputInfo)
			: base(outputInfo) {
			Depth = 0;
			_generators.Add(typeof(Ast.CodeBlockExpression), new ReCodeBlockGenerator(this));
			_generators.Add(typeof(Ast.FunctionCallExpression), new ReFunctionCallGenerator(this));
			_generators.Add(typeof(Ast.AssignmentExpression), new ReAssignmentGenerator(this));
			_generators.Add(typeof(Ast.FunctionDefExpression), new ReFunctionDefGenerator(this));
			_generators.Add(typeof(Ast.ProgramDefExpression), new ReProgramDefGenerator(this));
			_generators.Add(typeof(Ast.ReturnExpression), new ReReturnGenerator(this));
			_generators.Add(typeof(Ast.UnaryExpression), new ReUnaryGenerator(this));
			_generators.Add(typeof(Ast.BinaryExpression), new ReBinaryGenerator(this));
			_generators.Add(typeof(Ast.ConstantExpression), new ReConstantGenerator(this));

			_generators.Add(typeof(Ast.VariableExpression), new ReVariableGenerator(this));
			_generators.Add(typeof(Ast.InstanceVariableExpression), new ReInstanceVariableGenerator(this));
			_generators.Add(typeof(Ast.ClassReferenceExpression), new ReClassReferenceGenerator(this));

			_generators.Add(typeof(Ast.ClassDefExpression), new ReClassDefGenerator(this));
			_generators.Add(typeof(Ast.ConditionalExpression), new ReConditionalGenerator(this));
			_generators.Add(typeof(Ast.WhileExpression), new ReWhileGenerator(this));
		}
		public override void GenerateCode(Ast.Expression ast) {
			var assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(_outputInfo.OutputName));
			var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
				AssemblyBuilderAccess.Save);
			var moduleBuilder = assemblyBuilder.DefineDynamicModule(_outputInfo.OutputName);
			Set<ModuleBuilder>(MODULE_BUILDER_KEY,moduleBuilder);
			Set<AssemblyBuilder>(ASSEMBLY_BUILDER_KEY,assemblyBuilder);
			EmitExpression(ast);
			assemblyBuilder.Save(_outputInfo.OutputName);
		}
		public void EmitExpression(Ast.Expression expr) {
			_generators[expr.GetType()].Generate(expr);
		}
		public void Set<T>(string key, object value) {
			var add = (key == IL_GENERATOR_KEY) ? Depth.ToString() : "";
			Require.HasType(value,typeof(T));
			_params[key+add] = value;
		}
		public T Get<T>(string key) {
			var add = (key == IL_GENERATOR_KEY) ? Depth.ToString() : "";
			return (T) _params[key+add];
		}

		public List<Type> FunctionDefClasses {
			get { return _functionDefClasses; }
		}

		public int Depth { get; set; }

		public string CurrentScope {
			get {
				if (_classScopeStack.Count == 0)
					return ClassNames.GlobalScope;
				return _classScopeStack.Peek();
			}
		}

		public void EnterScope(string className) {
			_classScopeStack.Push(className);
		}

		public void LeaveScope(string className) {
			_classScopeStack.Pop();
		}
	}
}
